import re
from subprocess import Popen, TimeoutExpired, PIPE
import pandas as pd
import os
import sys


# 递归列出所有文件的路径
def list_all_files(dirname: str, file_type: str):
    content_list = os.listdir(dirname)
    all_files_list = []
    for content in content_list:
        path_name = '/'.join([dirname, content])
        if os.path.isfile(path_name):
            if path_name.endswith('.' + file_type):
                all_files_list.append(path_name)
        elif os.path.isdir(path_name):
            all_files_list.extend(list_all_files(path_name, file_type))
    return all_files_list


# 返回转换后的in文件(打开之后再transfrom)
def transform_in_file(in_path: str):
    pattern_block = re.compile(r'^//.*\n$')  # 匹配单行注释
    pattern_inline = re.compile(r'\s*//.*')  # 匹配行间注释

    with open(in_path) as fr:
        texts = fr.readlines()
        texts_removed = ['10000\n', '1234\n']  # 头部添加10000、1234

        # 去注释
        for text in texts:
            text = text.strip() + '\n'
            if not pattern_block.match(text):
                text_removed = pattern_inline.sub('', text)
                if text_removed:
                    texts_removed.append(text_removed)

        texts_removed.extend(['\n', 'print\n', 'Quit\n'])  # 尾部添加print、quit
        return texts_removed


# 模拟用户输入,并返回状态码,0为跑通，-1为超时
def auto_test(in_path: str, program_path: str) -> int:
    err_code = 0

    texts_in = transform_in_file(in_path)

    with Popen(args='' + program_path, stdin=PIPE, stdout=PIPE, stderr=PIPE, shell=True, encoding='utf-8') as proc:
        try:
            proc.stdin.writelines(texts_in)
            proc.stdin.flush()
            proc.communicate(''.join(texts_in), timeout=1.)

        except TimeoutExpired:
            proc.kill()
            err_code = -1

    return err_code


# 存储程序的print结果
def save_print_file(origin_path_name: str, text_lines) -> None:

    pattern_root = re.compile(r'(^(?:\.\./)*(?:\./)?(?:/)?.*?)/')
    pattern_file = re.compile(r'[^/]+$')  # 新建文件时要先建文件夹

    path_name_removed = pattern_root.sub(r'\1_print/', origin_path_name)
    dir_name_removed = pattern_file.sub('', path_name_removed)

    if not os.path.exists(dir_name_removed):
        os.makedirs(dir_name_removed)  # 新建文件夹
    with open(path_name_removed, mode='w') as fr:
        fr.writelines(text_lines)


def dict_append(tmp_dict, out_name: str, row_num: int, out: str, out_out: str) -> None:
    tmp_dict['out_name'].append(out_name)
    tmp_dict['row_num'].append(row_num)
    tmp_dict['out'].append(out)
    tmp_dict['out_out'].append(out_out)


def match_out(out_path: str, out_out_path: str, tmp_dict, err_code: int):
    # 程序超时
    if err_code == -1:
        dict_append(tmp_dict, out_path, '超时', '', '')
        return -1

    # 程序跑通了
    with open(out_path) as fr:
        out_lines = fr.readlines()
    with open(out_out_path) as fr:
        try:
            out_out_lines = fr.readlines()
        except UnicodeDecodeError:
            # print(out_path + '对应的输出文件格式有误')
            dict_append(tmp_dict, out_path, '格式', '', '')
            return -1

    out_lines_transformed = list(map(lambda x: x.strip().lower(), out_lines))
    out_out_lines_transformed = list(
        map(lambda x: x.strip().lower(), out_out_lines))

    out_lines_transformed[:] = filter(lambda x: x, out_lines_transformed)
    out_out_lines_transformed[:] = filter(
        lambda x: x, out_out_lines_transformed)

    out_lines_transformed.sort()
    out_out_lines_transformed.sort()

    save_print_file(out_path, out_out_lines)
    wrong = 0

    if len(out_lines_transformed) != len(out_out_lines_transformed):
        # print(out_path + '对应的输出文件行数不一致')
        dict_append(tmp_dict, out_path, '行数', '', '')
        return -1

    for idx in range(len(out_lines_transformed)):
        if out_lines_transformed[idx].lower() != out_out_lines_transformed[idx].lower():
            dict_append(tmp_dict, out_path, str(idx + 1),
                        out_lines_transformed[idx], out_out_lines_transformed[idx])
            wrong = 1
            # break
    # 没有差异
    if not wrong:
        dict_append(tmp_dict, out_path, 'pass', 'pass', 'pass')
        return 0
    return -1


def g_or_r(val: str):
    if not isinstance(val, str) or '/' in val:
        return ''
    if val == 'pass':
        return 'background-color:green'
    return 'background-color:red'


def match_all_out(program_path: str, dir_path: str, out_out_dir):
    all_in_files = sorted(list_all_files(dir_path, 'in'))
    all_out_files = sorted(list_all_files(dir_path, 'out'))

    tmp_dict = {
        'out_name': [],
        'row_num': [],
        'out': [],
        'out_out': []
    }
    count_dict = {
        'pass_num': 0,
        'fail_num': 0,
    }
    for idx, in_file in enumerate(all_in_files):
        err_code = auto_test(in_file, program_path)
        if match_out(all_out_files[idx], out_out_dir + 'out_test.out', tmp_dict, err_code):
            count_dict['fail_num'] += 1
        else:
            count_dict['pass_num'] += 1

    count_dict['all_num'] = count_dict['fail_num'] + count_dict['pass_num']
    count_dict['pass_rate'] = count_dict['pass_num'] / count_dict['all_num']
    count_dict['fail_rate'] = count_dict['fail_num'] / count_dict['all_num']

    res_pd = pd.DataFrame(tmp_dict)
    res_pd.to_csv('match.csv', index=None)
    res_pd.style.applymap(g_or_r).to_excel('match.xlsx', index=None)

    print('total:', count_dict['all_num'])
    print('passed:', count_dict['pass_num'],
          '\trate:', count_dict['pass_rate'])
    print('failed:', count_dict['fail_num'],
          '\trate:', count_dict['fail_rate'])
    return 0 if count_dict['fail_num'] == 0 else -1


def main():
    if len(sys.argv) != 3:
        print('python auto_test.py main test_dir')
        return
    program_path = sys.argv[1] if sys.argv[1].startswith(
        './') else './' + sys.argv[1]
    dir_path = sys.argv[2]

    if match_all_out(program_path, dir_path, ''):
        sys.exit(-1)


if __name__ == '__main__':
    main()